########################################################################
#                                                                      #
#               This software is part of the ast package               #
#          Copyright (c) 1989-2011 AT&T Intellectual Property          #
#                      and is licensed under the                       #
#                 Eclipse Public License, Version 1.0                  #
#                    by AT&T Intellectual Property                     #
#                                                                      #
#                A copy of the License is available at                 #
#          http://www.eclipse.org/org/documents/epl-v10.html           #
#         (with md5 checksum b35adb5213ca9657e911e9befb180842)         #
#                                                                      #
#              Information and Software Systems Research               #
#                            AT&T Research                             #
#                           Florham Park NJ                            #
#                                                                      #
#                 Glenn Fowler <gsf@research.att.com>                  #
#                                                                      #
########################################################################

# Make a shell archive

function logmsg
{
	print -ru2 $command: "$@"
}

function err_exit
{
	logmsg "$@"
	exit 1
}

trap 'rm -rf /tmp/shar$$*' EXIT
trap 'err_exit qutting early' HUP INT QUIT TERM

case $(getopts '[-][123:xyz]' opt --xyz 2>/dev/null; echo 0$opt) in
0123)	usage=$'
[-?
@(#)$Id: shar (AT&T Labs Research) 1999-04-20 $
]
'$USAGE_LICENSE$'
	[+NAME? shar - create a shell archive]
	[+DESCRIPTION?\bshar\b reads one or more input files and creates a
		shell script which when executed will restore the contents
		of these files. This is called a shell archive or \ashar\a.
		The resulting archive is sent to standard output unless the
		\b-o\b option is specified.]
	[a:net-headers?Automatic generation of headers. The \b-n\b option
		must also be specified. If the archive name does not
		contain a \b/\b, then \b/part\b will be append to the
		given archive name when constructing the header.]
	[b:bits-per-code]:[bits?When doing compression, use \b-b \b\abits\a
		as a parameter to \bcompress\b(1). The \b-b\b option turns on
		\b-Z\b.]
	[c:cut-mark?Start the shar with a cut line.]
	[d:here-delimiter]:[string?Use \astring\a to delimit the files 
		in the shar instead of \bSHAR_EOF\b.]
	[f:basenames?Use the basenames for the files.]
	[g:level-for-gzip]:[level?When doing compression, use \b-\b\alevel\a
		as a parameter to \bgzip\b(1). The \b-g\b option turns on
		\b-z\b.]
	[l:whole-size-limit]#[size?Limit the output file size to \asize\a
		bytes. A suffix of \bb\b, \bk\b, or \bm\b can be specified
		to indicate 512-byte blocks, kilobytes, or megabytes
		respectively.]
	[n:archive-name:name]:[name?Override automatically determined name
		for the archive with \aname\a.]
	[o:output-prefix]:[prefix?Save the archive files \aprefix\a\b.01\b
		through \aprefix\a\b.\b\ann\a instead of standard output. This
		option must be used when \b-l\b or the \b-L\b is specified.]
	[p:intermix-type?Allow positional parameter options. The options
		\b-B\b, \b-T\b, \b-z\b and \b-Z\b may be embedded, and files
		to the right of the option will be processed in the specified
		mode.]
	[q:quit|silent?Do not output verbose messages locally when producing
		the archive.]
	[s:submitter]:[user?Override automatically determined submitter name
		with \auser\a which is of the form \awho\a\b@\b\awhere\a.]
	[t:tty?Write errors and the name of each file to \b/dev/tty\b as it is
		archived.]
	[w:no-character-count?Do NOT check each file with \bwc -c\b after
		unpack.]
	[z:gzip?\bgzip\b(1) and \buuencode\b(1) all files prior to packing.]
	[B:uuencode,binary-files-files?Treat all files as binary and
		\buuencode\b(1) prior to packing.]
	[D:no-md5-digest?Do NOT use \bcksum md5sum\b digest to verify 
		the unpacked files. The default is to check.]
	[L:split-size-limit]#[size?Limit the output file size to \asize\a
		bytes as with \b-l\b, but split the archive into multiple 
		files.]
	[Q:quiet-unshar?Verbose OFF. Disables the inclusion of comments to
		be output when the archive is unpacked.]
	[M:mixed-uuencode?Mixed mode. Determine if the files are text or
		binary and archive correctly. Files found to be binary 
		are uuencoded prior to packing. This is the default.]
	[S:stdin-file-list?Read list of files to be packed from the standard
		input rather than from the command line in the format
		generated by \bfind\b(1) and \btw\b(1). If \b-p\b is specified,
		the options \b-B\b, \b-T\b, \b-z\b and \b-Z\b must be
		included in the standard input.]
	[T:text-files?Treat all files as text.]
	[X:query-user?When unpacking, ask the user if files should be 
		overwritten.]
	[Z:compress?\bcompress\b(1) and \buuencode\b(1) all files prior to
		packing.]

	[files ...]

	[+SEE ALSO?\bcksum\b(1), \bcompress\b(1), \bfind\b(1), \bgzip\b(1),
		\bpax\b(1), \btw\b(1), \buuencode\b(1), \bwc\b(1)]
    '
    ;;
*)
    usage='ab:[bits]cd:[delim]fg:[level]l#[size]n:[name]o:[prefix]pqs:[who]tzBDL#[size]MQSXTZ files ...'
    ;;
esac

IFS=$'\n'
command=${0##*/}
user=${USER:-${LOGNAME:-$(whoami 2>/dev/null || who am i | sed $'s/[ \t].*//')}}@$(hostname)
integer size=0
flags= prefix= mode=M arg=
separator=SHAR_EOF
# some strings that should be internationalized some day
skipping=$"echo 'x -' SKIPPING"
exists=$"'(file already exists)'"
extract=$"echo 'x -' extracting"
mkdir=$"creating directory"
uncomp=$"uncompressing file"

while getopts "$usage" c
do 
	case $c in
	[acfpqwDSQX])
		flags=$flags$c;;
	b)	mode=Z;arg=-"b $OPTARG";;
	g)	mode=z;arg=-$OPTARG;;
	l)	size=OPTARG;;
	L)	split=1 size=OPTARG;;
	d)	separator=$OPTARG;;
	n)	aname=$OPTARG;;
	o)	prefix=$OPTARG
		exec >  /tmp/shar$$.1;;
	s)	user=$OPTARG;;
	q)	verbose=;;
	t)	verbose=1; exec 2> /dev/tty;;
	v)	verbose=1;;
	[zZBMT])
		mode=$c;;
	esac
done

# Check remaining arguments, which should be just a list of files:
shift $((OPTIND-1))
if	(( $# ==0 )) && [[ $flags != *S* ]]
then    err_exit "no arguments left!"
fi

contents=''     # no files so far.
contdirs=''     # no directories so far.

function preprocess_files
{
	typeset file=$1
	if	[[ $flags == *p* && $file == -[zBTZ]  ]]
	then	mode=${file#-}
	elif	[[ -f $file ]]
	then	if	[[ $flags == *f* ]]
		then	contents="$contents$IFS${file##*/}"
		else	contents="$contents$IFS$file"
		fi
	elif	[[ -d $file ]]
	then	if	[[ $flags == *f* ]]
		then	err_exit "cannot archive directory $file with -b option."
		fi
		contdirs="$contdirs$IFS$file/ "

	else    err_exit "cannot archive $file"
	fi
}

if	[[ $flags == *S* ]]
then	while	read -r file
	do	preprocess_files "$file"
		print -r -- "$file"
	done > /tmp/shar$$.3
else	for file
	do	preprocess_files "$file"
	done
fi


if	[[ $flags == *a* ]]
then	if	[[ $aname ]]
	then	if	[[ $aname != */* ]]
		then	aname=$aname/part
		fi
		cat <<- !!!
		Submitted-by:  $user
		Archive-name: ${aname}01

		!!!
	else	err_exit "Cannot use -a without -n"
	fi
fi

# Generate the prologue
# (The leading newline is for those who type csh instead of sh.)

if	[[ $flags == *c* ]]
then	print -r -- '#---- Cut Here and feed the following to sh ----'
fi
cat <<!!!

# This is a shell archive (produced by a ksh93 script).
# Remove anything before this line, then unpack it by saving 
# it in a file and typing "sh file".
#
# Wrapped by on $(date) by $user
# Source directory was \`$(pwd)'.
#
# This shar contains:
# length   mode       name
# ------   ---------- ------------------------------------------
!!!
ls -df "#%8(size)d %(mode)s %(name)s" $contdirs $contents
print '\n#\nnocheck=$1'

if	[[ $flags == *Q* ]]
then	print quiet=:
else	print quiet=false
fi

if	[[ $flags == *x* ]]
then	print nocheck=-c
fi

if	[[ $flags != *D* ]]
then	cat	<<- \!!!
	if	uncompress < /dev/null > /dev/null 2>&1
	then	uncompress='uncompress -f'
	elif	gunzip < /dev/null > /dev/null 2>&1
	then	uncompress='gunzip -f'
	fi

	if	md5sum /dev/null > /dev/null 2>&1
	then	md5=md5sum
	elif	cksum -x md5 /dev/null > /dev/null 2>&1
	then	md5='cksum -x md5'
	fi

	if	touch -am 123456789 shar$$.3 > /dev/null 2>&1 && test -f shar$$.3
	then	touch=touch
	else	touch=:
	    echo  'WARNING: not restoring timestamps.'
	fi
	rm -f 123456789 shar$$.3

	!!!
fi

if	[[ $flags == *X* ]]
then	cat <<- \!!!
	#
	# The unshar will be interactively queried
	#
	if	test ! -t 2
	then	exec < /dev/tty
	fi
	if	test	"`echo -n | wc -c`" -gt 0
	then	nflag= cflag='\c'
	else	nflag=-n cflag=
	fi
	!!!
fi


function isbinary #file
{
	[[ $(file -m /dev/null -M "$1") == @(*/ebcdic*|*/octet-stream|*system*/*) ]]
}

function emit_guard # file type
{
	cat <<- !!!
	  echo \$nflag '? -' overwrite "$name" '[no, yes, all, quit] (no)?' \$cflag
	  read check
	  case \$check in
	  [Aa]*)  nocheck=-c;;
	  [Qq]*)  echo 'extraction aborted';exit 1;;
	  [Yy]*)	;;
	  *)	skip=1;;
	  esac
	fi
	if	test "\$skip" = 1 
	then
	!!!
}

function makedirs # file
{
	typeset file=$1
	file=$(dirname "$file")
	if	[[ $file == @(.|/|//) ]]
	then	return
	fi
	makedirs "$file"
	cat <<- !!!
	if	test ! -d '$file'
	then	test X"\$quiet" != X &&  echo 'x -' '$mkdir' '$file'
	        mkdir '$file'
	fi
	!!!
}

# Emit the files and their separators:
function pack_file
{
	typeset file=$1
	filetype=text binary=
	if	[[ -d $file ]]
	then	filteype=directory
	else	case $mode in
		B)	filetype=binary;;
		z)	filetype=gzipped;;
		Z)	filetype=compressed;;
		M)	if	isbinary "$file" 
			then	filetype=binary
				binary=1
			fi
		esac
	fi
	# Decide which name to archive under.
	if	[[ $flags == *f* ]]
	then	name=${file##*/}
		[[ $flags != *q* ]]  && logmsg "a - $name [from $file] ($filetype)"
	else	name="$file"
		[[ $flags != *q* ]]  && logmsg "a - $file ($filetype)"
	fi

	print -r "# ============= $file =============="
	if	[[ $file == */* ]]
	then	makedirs "$file"
	fi
	# Emit either a mkdir or a cat/sed to extract the file.
	if [[ -d $file ]]
	then	print "mkdir $file"
		[[ flags != *Q* ]] && print "echo mkdir -- $file"
	else	filetype=text binary=
		print 'skip='
		printf "if\ttest -f %q && test \"X\$nocheck\" != X-c\nthen\t" "$file"
		if	[[ $flags == *X* ]]
		then	emit_guard
		fi
		printf "\$quiet || $skipping %q $exists\nelse \$quiet || $extract %q '(%s)'\n" "$file" "$file" "$filetype"
		if	[[ $mode == [BZz] || $binary ]]
		then	if	[[ $mode == Z ]]
			then	compress $arg < $file > /tmp/shar$$.2
				cname=$name.Z
				dname=/tmp/shar$$.2
			elif	[[ $mode == z ]]
			then	gzip $arg < $file > /tmp/shar$$.2
				dname=/tmp/shar$$.2
				cname=$name.gz
			else	dname=$file
				cname=$name
			fi
			print -r 'uudecode  << \!!!!'
			uuencode "$cname" < $dname
			print	"!!!!"
			if	[[ $mode == Z  && flags != *Q* ]]
			then	printf "echo '$uncomp' %s\n\$uncompress  %q\n" "$file" "$cname"
			elif	[[ $mode == z  && flags != *Q* ]]
			then	printf "echo '$uncomp' %s\ngunzip  %q\n" "$file" "$cname"
			fi
		else	print -r "sed 's/^@//' > \"$name\" <<'$separator'"
			sed  -e 's/^[.~@]/@&/'  -e 's/^From/@&/'  "$file"
			print -r $separator
		fi
	fi

	# Emit chmod to set permissions on the extracted file;
	# this keels over if the filename contains "?".
	fmode=$(ls -df "%(mode)s" "$file")
	fmode=${fmode/?@(???)@(???)@(???)/u=\1,g=\2,o=\3}
	mtime=$(date -m -f %# "$file")
	printf "chmod ${fmode//-} %q&&\n\t\$touch -am $mtime %q\n" "$name" "$name"
	print "fi"
}

if	[[ $flags == *S* ]]
then	while read -r file
	do	pack_file "$file"
	done < /tmp/shar$$.3
else	for file
	do	pack_file "$file"
	done
fi

# If the -c option was given, emit the checking epilogue:
# If the receiving machine has cksum, it will be used instead of wc

files=$(printf "%q "  $contents)
if [[ $flags != *w*  && $flags != *D* ]]
then	cat <<-  __END__
	\$quiet || echo 'Inspecting for damage in transit...'
	temp=/tmp/shar\$\$; dtemp=/tmp/.shar\$\$
	trap "rm -f \$temp \$dtemp; exit" EXIT HUP INT QUIT TERM
	if	[ X"\$md5" != X ]
	then	cat > \$temp <<\!!!
		$(
			if	[[ $flags == *b* ]]
			then	cksum -x md5 "$@" | sed 's=[^ ]*/==' 
			else	cksum -x md5  $contents | sed 's=[^ ]*/=='
			fi
		)
		!!!
		cksum='cksum -x md5'
	else	cat > \$temp <<\!!!
		$(
			IFS=$'\n'	# this line should not be needed
			if	[[ $flags == *b* ]]
			then	wc  "$@" | sed 's=[^ ]*/==' 
			else	wc  $contents | sed 's=[^ ]*/=='
			fi
		)
		!!!
		cksum=wc
	fi
	\$cksum $files | sed 's=[^ ]*/==' | diff -b \$temp - >\$dtemp
	if	[ -s \$dtemp ]
	then	echo "Ouch [diff of \$cksum output]:" ; cat \$dtemp
	else	\$quiet || echo "No problems found."
	fi
	__END__
fi

# split or limit size 
if	[[ $prefix ]]
then	if	[[ $split ]]
	then	split -b $size -f "$prefix" /tmp/shar$$.1
	elif	(( $size && $(wc -c < /tmp/shar$$.1) > $size ))
	then	head -c $size /tmp/shar$$.1 > $prefix.01
	else	cp /tmp/shar$$.1 "$prefix.01"
	fi
fi

print 'exit 0'   # exit even if more input
